package ops.model.manager;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.util.Collections;
import java.util.Map;
import java.util.TreeMap;
import java.util.Map.Entry;

import ops.model.Entity;
import ops.model.EntityAlreadyExistsException;
import ops.util.IOHelper;

public abstract class EntityManager<E extends Entity> {

	private TreeMap<Object, E> map;
	
	public abstract File getFile();
	
	protected abstract Object getKeyForEntity(E element);
	
	public EntityManager() {
		load();
	}
	
	protected Map<Object, E> getModifiableMap() {
		return map;
	}
	
	public Map<Object, E> getMap() {
		return Collections.unmodifiableMap(map);
	}
	
	public E getObjectByKey(Object key) {
		return map.get(key);
	}
	
	public E remove(Object key) {
		return map.remove(key);
	}
	
	public void add(E element) throws EntityAlreadyExistsException {
		Object key = getKeyForEntity(element);
		
		if (map.containsKey(key)) {
			throw new EntityAlreadyExistsException("An entity with code '"+key+"' already exists");
		}

		map.put(key, element);
	}
	
	/**
	 * This is a helper method to return the next available ID.
	 * NOTE: this method will only work for managers that handle entities which code is a NUMBER!
	 */
	private Object nextID() {
		Entry<Object, E> entry = map.lastEntry();
		if (entry == null) {
			// if the map is empty.
			return 1L;
		}
		
		E element = entry.getValue();
		Object code = getKeyForEntity(element);
		
		if (code instanceof Integer) {
			return ((Integer) code).longValue() + 1;
		} else if (code instanceof Long) {
			return ((Long) code) + 1;
		} else {
			return Integer.MIN_VALUE;
		}
	}
	
	public int nextIntID() {
		Long value = (Long) nextID();
		
		return value.intValue();
	}
	
	public long nextLongID() {
		return (Long) nextID();
	}
	
	@SuppressWarnings("unchecked")
	public void save() {
		ObjectOutputStream out = null;
		
		try {
			out = new ObjectOutputStream(new FileOutputStream(getFile()));
			out.writeObject(map);
		} catch (FileNotFoundException e) {
			e.printStackTrace();
		} catch (IOException e) {
			e.printStackTrace();
		} finally {
			IOHelper.close(out);
		}
	}
	
	@SuppressWarnings("unchecked")
	protected void load() {
		ObjectInputStream in = null;
		
		try {
			in = new ObjectInputStream(new FileInputStream(getFile()));
			map = (TreeMap<Object, E>) in.readObject();
		} catch (IOException e) {
			map = new TreeMap<Object, E>();
			save();
		} catch (ClassNotFoundException e) {
			e.printStackTrace();
		} finally {
			IOHelper.close(in);
		}	
	}
}



